import {
  ComponentPropsOptions,
  defineComponent,
  ExtractPropTypes,
  getCurrentInstance,
  SetupContext,
  DefineComponent,
  provide,
  inject,
} from 'vue';
import { ComponentEvent, useEvent } from './useEvent';

interface ComponentOptionType<PropsOptions, Props, Emits, Refer> {
  name?: string;
  props?: PropsOptions;
  providerRefer?: boolean;
  emits?: Emits;
  setup: (params: {
    props: Props;
    event: ComponentEvent<Emits>;
    setupContext: SetupContext<Emits>;
  }) => {
    refer?: Refer;
    render: () => any;
  };
}

export function designComponents<
  PropsOptions extends Readonly<ComponentPropsOptions>,
  Props extends Readonly<ExtractPropTypes<PropsOptions>>,
  Emits extends { [k: string]: (...args: any[]) => boolean },
  Refer
>(componentOption: ComponentOptionType<PropsOptions, Props, Emits, Refer>) {
  const { setup, providerRefer, emits, ...leftOptions } = componentOption;
  return {
    ...defineComponent({
      ...leftOptions,
      setup(props: Props, context: any) {
        const ctx = getCurrentInstance() as any;
        const event = useEvent<Emits>(emits!);

        if (!setup) {
          console.error('designComponents: setup is required');
          return () => null;
        }

        const { render, refer } = setup({ props, event, setupContext: context });

        if (!!refer) {
          const duplicateKey = Object.keys(leftOptions.props || {}).find(i =>
            Object.prototype.hasOwnProperty.call(refer as any, i),
          );
          if (!!duplicateKey) {
            console.error(`designComponent: duplicate key ${duplicateKey} in refer`);
          } else {
            Object.assign(ctx.proxy, refer);
          }
        }

        if (providerRefer) {
          if (!leftOptions.name) {
            console.error('designComponents: name is required when providerRefer is true');
          }
          provide(`@@${leftOptions.name}`, refer);
        }

        return render;
      },
    } as any),
    // 表示必须在 特定的场合 setup 中执行
    use: {
      ref: (refName: string) => {
        const ctx = (getCurrentInstance() as any).ctx;
        return {
          get value() {
            return ctx.refs[refName] as Refer | null;
          },
        };
      },
      inject: (defaultValue?: any) => {
        return inject(`@@${leftOptions.name}`, defaultValue) as Refer;
      },
    },
  };
}

// const Button = designComponents({
//   name: 'pl-button',
//   props: {
//     age: {
//       type: Number,
//       default: 10,
//     },
//     flag: {
//       type: Boolean,
//       required: true,
//     },
//     label: { type: String },
//   },
//   setup(props, ctx) {
//     return {
//       render: () => null,
//     };
//   },
// });
